﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ttbit.math
{
    //用system.drawing.color 替代
    //public struct Color32
    //{
    //    public Color32(byte r = 255, byte g = 255, byte b = 255, byte a = 255)
    //    {
    //        this.r = r;
    //        this.g = g;
    //        this.b = b;
    //        this.a = a;
    //    }
    //    public byte r;
    //    public byte g;
    //    public byte b;
    //    public byte a;
    //    public Color ToColor()
    //    {
    //        return Color.FromArgb(a, r, g, b);
    //    }
    //}
    public struct ColorF
    {
        public Color ToColor32()
        {
            int ir = (int)(R * 255);
            int ig = (int)(G * 255);
            int ib = (int)(B * 255);
            int ia = (int)(A * 255);
            if (ir < 0) ir = 0;
            if (ir > 255) ir = 255;
            if (ig < 0) ig = 0;
            if (ig > 255) ig = 255;
            if (ib < 0) ib = 0;
            if (ib > 255) ib = 255;
            if (ia < 0) ia = 0;
            if (ia > 255) ia = 255;
            return Color.FromArgb(ia, ir, ig, ib);
        }
        //public ColorF(float r = 1.0f, float g = 1.0f, float b = 1.0f, float a = 1.0f)
        //{
        //    this.R = r;
        //    this.G = g;
        //    this.B = b;
        //    this.A = a;
        //}
        public float R;
        public float G;
        public float B;
        public float A;

        public static ColorF White
        {
            get
            {
                return ColorF.FromRGBA(1, 1, 1, 1);
            }
        }
        public static ColorF Red
        {
            get
            {
                return ColorF.FromRGBA(1, 0, 0, 1);
            }
        }
        public static ColorF Black
        {
            get
            {
                return ColorF.FromRGBA(0, 0, 0, 1);
            }
        }
        public static void ToHSV(ColorF c, out int h_degree, out float s, out float v)
        {
            float min, max;

            min = c.R < c.G ? c.R : c.G;
            min = min < c.B ? min : c.B;
            max = c.R > c.G ? c.R : c.G;
            max = max > c.B ? max : c.B;

            // H
            h_degree = 0;
            if (max == min)
            {
                h_degree = 0;
            }
            else if (max == c.R && c.R > c.B)
            {
                h_degree = (int)(60 * (c.G - c.B) * 1.0f / (max - min) + 0);
            }
            else if (max == c.R && c.G < c.B)
            {
                h_degree = (int)(60 * (c.G - c.B) * 1.0f / (max - min) + 360);
            }
            else if (max == c.G)
            {
                h_degree = (int)(60 * (c.B - c.R) * 1.0f / (max - min) + 120);
            }
            else if (max == c.B)
            {
                h_degree = (int)(60 * (c.R - c.G) * 1.0f / (max - min) + 240);
            }
            // S
            if (max == 0)
            {
                s = 0;
            }
            else
            {
                s = (max - min) * 1.0f / max;
            }
            // V
            v = max;
        }
        public static ColorF FromHSV(int h_degree, float s = 1.0f, float v = 1.0f)
        {
            ColorF c = ColorF.White;
            if (s == 0)
                c.R = c.G = c.B = v;
            else
            {
                float h = h_degree / 60.0f;


                int i = (int)h % 6;
                float C = h - i;

                float X = v * (1 - s);
                float Y = v * (1 - s * C);
                float Z = v * (1 - s * (1 - C));
                switch (i)
                {
                    case 0: c.R = v; c.G = Z; c.B = X; break;
                    case 1: c.R = Y; c.G = v; c.B = X; break;
                    case 2: c.R = X; c.G = v; c.B = Z; break;
                    case 3: c.R = X; c.G = Y; c.B = v; break;
                    case 4: c.R = Z; c.G = X; c.B = v; break;
                    case 5: c.R = v; c.G = X; c.B = Y; break;
                }
            }
            return c;
        }

        public static ColorF FromH(int h360)//输入一个0到360的值
        {
            ColorF c = ColorF.White;
            //h360 = 60
            //sin插值
            var v = (h360 % 120 / 120.0f);
            if (v < 0) v += 1;

            var f = MathF.Sin(v * MathF.PI) * 0.5f;
            if (v > 0.5)
                f = 1 - f;
            //直接插值
            //let f = h360 % 120 / 120;
            if (h360 < 120)  //b->r
            {

                if (f <= 0.5)
                {
                    c.B = 1.0f;
                    c.R = (f) / (1 - f);
                }
                else
                {
                    c.R = 1.0f;
                    c.B = (1 - f) / f;
                }
                c.G = 0;
            }
            else if (h360 < 240) //r->g
            {

                if (f <= 0.5)
                {
                    c.R = 1.0f;
                    c.G = 1.0f * f / (1 - f);
                }
                else
                {
                    c.G = 1.0f;
                    c.R = 1.0f * (1 - f) / f;
                }
                c.B = 0;

            }
            else //g->b
            {

                c.R = 0;
                if (f <= 0.5)
                {
                    c.G = 1.0f;
                    c.B = 1.0f * f / (1 - f);
                }
                else
                {
                    c.B = 1.0f;
                    c.G = 1.0f * (1 - f) / f;
                }
            }
            return c;
        }
        public void SetV(float v)
        {
            if (v <= 1.0)
            {
                this.R *= (v);
                this.G *= (v);
                this.B *= (v);
            }
            else
            {

                var maxr = v - 1;
                this.R = this.R * (1 - maxr) + maxr;
                this.G = this.G * (1 - maxr) + maxr;
                this.B = this.B * (1 - maxr) + maxr;
            }
        }
        public static ColorF FromColor32(System.Drawing.Color c)
        {
            return ColorF.FromRGBA(c.R / 255.0f, c.G / 255.0f, c.B / 255.0f, c.A / 255.0f);
        }
        public static ColorF FromRGBA(float r, float g, float b, float a = 1.0f)
        {
            return new ColorF() { R = r, G = g, B = b, A = a };
        }
        public static ColorF FromARGB(float a, float r, float g, float b)
        {
            return new ColorF() { R = r, G = g, B = b, A = a };
        }
    }

    public struct Color_Lab
    {
        public float l;
        public float a;
        public float b;

        const float Xn = 0.950456f;
        const float Yn = 1.0f;
        const float Zn = 1.088754f;
        public void FromRGB(float r, float g, float b)
        {
            r = gamma(r);
            g = gamma(g);
            b = gamma(b);

            float X = 0.4124564f * r + 0.3575761f * g + 0.1804375f * b;
            float Y = 0.2126729f * r + 0.7151522f * g + 0.0721750f * b;
            float Z = 0.0193339f * r + 0.1191920f * g + 0.9503041f * b;
            float fX, fY, fZ;
            X /= (Xn);
            Y /= (Yn);
            Z /= (Zn);
            if (Y > 0.008856)
            {
                fY = MathF.Pow(Y, 1.0f / 3.0f);

            }
            else
            {
                fY = 7.787f * Y + 16.0f / 116.0f;
            }
            if (X > 0.008856)
                fX = MathF.Pow(X, 1.0f / 3.0f);
            else
                fX = 7.787f * X + 16.0f / 116.0f;

            if (Z > 0.008856)
                fZ = MathF.Pow(Z, 1.0f / 3.0f);
            else
                fZ = 7.787f * Z + 16.0f / 116.0f;
            this.l = (116.0f * Y - 16.0f);
            //亮度保留负值会有更好的比对效果
            //this.l = l > 0.0f ? l : 0.0f;
            this.a = (fX - fY) * 500;
            this.b = (fY - fZ) * 200;
        }
        //    static gamma(x: number) : number {
        //    return x > 0.04045 ? Math.pow((x + 0.055) / 1.055, 2.4) : (x / 12.92);
        //};
        static float gamma(float x)
        {
            return x > 0.04045 ? MathF.Pow((x + 0.055f) / 1.055f, 2.4f) : (x / 12.92f);
        }
        //float gamma(float x)
        //{
        //    return x > 0.04045 ? powf((x + 0.055f) / 1.055f, 2.4f) : (x / 12.92);
        //};

        void RGB2XYZ(byte R, byte G, byte B, ref float X, ref float Y, ref float Z)
        {
            float RR = gamma(R / 255.0f);
            float GG = gamma(G / 255.0f);
            float BB = gamma(B / 255.0f);

            X = 0.4124564f * RR + 0.3575761f * GG + 0.1804375f * BB;
            Y = 0.2126729f * RR + 0.7151522f * GG + 0.0721750f * BB;
            Z = 0.0193339f * RR + 0.1191920f * GG + 0.9503041f * BB;
        }



        void XYZ2Lab(float X, float Y, float Z)
        {
            float fX, fY, fZ;

            X /= (Xn);
            Y /= (Yn);
            Z /= (Zn);

            if (Y > 0.008856f)
                fY = MathF.Pow(Y, 1.0f / 3.0f);
            else
                fY = 7.787f * Y + 16.0f / 116.0f;

            if (X > 0.008856f)
                fX = MathF.Pow(X, 1.0f / 3.0f);
            else
                fX = 7.787f * X + 16.0f / 116.0f;

            if (Z > 0.008856)
                fZ = MathF.Pow(Z, 1.0f / 3.0f);
            else
                fZ = 7.787f * Z + 16.0f / 116.0f;

            this.l = 116.0f * fY - 16.0f;
            this.l = l > 0.0f ? l : 0.0f;
            a = 500.0f * (fX - fY);
            b = 200.0f * (fY - fZ);
        }


        public static float QuickDist(Color_Lab a, Color_Lab b)
        {
            var L = (a.l - b.l);
            var A = (a.a - b.a);
            var B = (a.b - b.b);
            return MathF.Sqrt(
                L * L + A * A + B * B
            );
        }
        static float degrees(float n) { return n * (180.0f / MathF.PI); }
        static float radians(float n) { return n * (MathF.PI / 180.0f); }

        static float hp_f(float x, float y) //(7)
        {
            if (x == 0 && y == 0)
                return 0;
            else
            {
                var tmphp = degrees(MathF.Atan2(x, y));
                if (tmphp >= 0)
                    return tmphp;
                else
                    return tmphp + 360;
            }
        }

        static float dhp_f(float C1, float C2, float h1p, float h2p) //(10)
        {
            if (C1 * C2 == 0) return 0;
            else if (Math.Abs(h2p - h1p) <= 180) return h2p - h1p;
            else if ((h2p - h1p) > 180) return (h2p - h1p) - 360;
            else if ((h2p - h1p) < -180) return (h2p - h1p) + 360;
            else throw (new Exception());
        }

        static float a_hp_f(float C1, float C2, float h1p, float h2p)
        { //(14)
            if (C1 * C2 == 0) return h1p + h2p;
            else if (Math.Abs(h1p - h2p) <= 180) return (h1p + h2p) / 2.0f;
            else if ((Math.Abs(h1p - h2p) > 180) && ((h1p + h2p) < 360)) return (h1p + h2p + 360) / 2.0f;
            else if ((Math.Abs(h1p - h2p) > 180) && ((h1p + h2p) >= 360)) return (h1p + h2p - 360) / 2.0f;
            else throw (new Exception());
        }
        public static float ciede2000(Color_Lab c1, Color_Lab c2)
        {
            /**
             * Implemented as in "The CIEDE2000 Color-Difference Formula:
             * Implementation Notes, Supplementary Test Data, and Mathematical Observations"
             * by Gaurav Sharma, Wencheng Wu and Edul N. Dalal.
             */

            // Get L,a,b values for color 1
            var L1 = c1.l;
            var a1 = c1.a;
            var b1 = c1.b;

            // Get L,a,b values for color 2
            var L2 = c2.l;
            var a2 = c2.a;
            var b2 = c2.b;

            // Weight factors
            var kL = 1;
            var kC = 1;
            var kH = 1;

            /**
             * Step 1: Calculate C1p, C2p, h1p, h2p
             */
            var C1 = MathF.Sqrt(MathF.Pow(a1, 2) + MathF.Pow(b1, 2)); //(2)
            var C2 = MathF.Sqrt(MathF.Pow(a2, 2) + MathF.Pow(b2, 2)); //(2)

            var a_C1_C2 = (C1 + C2) / 2.0f;             //(3)

            var G = 0.5f * (1 - MathF.Sqrt(MathF.Pow(a_C1_C2, 7.0f) /
                (MathF.Pow(a_C1_C2, 7.0f) + MathF.Pow(25.0f, 7.0f)))); //(4)

            var a1p = (1.0f + G) * a1; //(5)
            var a2p = (1.0f + G) * a2; //(5)

            var C1p = (float)MathF.Sqrt((float)MathF.Pow(a1p, 2) + MathF.Pow(b1, 2)); //(6)
            var C2p = (float)MathF.Sqrt((float)MathF.Pow(a2p, 2) + MathF.Pow(b2, 2)); //(6)

            var h1p = hp_f(b1, a1p); //(7)
            var h2p = hp_f(b2, a2p); //(7)

            /**
             * Step 2: Calculate dLp, dCp, dHp
             */
            var dLp = L2 - L1; //(8)
            var dCp = C2p - C1p; //(9)

            var dhp = dhp_f(C1, C2, h1p, h2p); //(10)
            var dHp = 2 * MathF.Sqrt(C1p * C2p) * MathF.Sin(radians(dhp) / 2.0f); //(11)

            /**
             * Step 3: Calculate CIEDE2000 Color-Difference
             */
            var a_L = (L1 + L2) / 2.0f; //(12)
            var a_Cp = (C1p + C2p) / 2.0f; //(13)

            var a_hp = a_hp_f(C1, C2, h1p, h2p); //(14)
            var T = 1 - 0.17f * MathF.Cos(radians(a_hp - 30)) + 0.24f * MathF.Cos(radians(2 * a_hp)) +
                0.32f * MathF.Cos(radians(3 * a_hp + 6)) - 0.20f * MathF.Cos(radians(4 * a_hp - 63)); //(15)
            var d_ro = 30 * MathF.Exp(-(MathF.Pow((a_hp - 275) / 25, 2))); //(16)
            var RC = MathF.Sqrt((MathF.Pow(a_Cp, 7.0f)) / (MathF.Pow(a_Cp, 7.0f) + MathF.Pow(25.0f, 7.0f)));//(17)
            var SL = 1 + ((0.015f * MathF.Pow(a_L - 50, 2)) /
                 MathF.Sqrt(20 + MathF.Pow(a_L - 50, 2.0f)));//(18)
            var SC = 1 + 0.045f * a_Cp;//(19)
            var SH = 1 + 0.015f * a_Cp * T;//(20)
            var RT = -2 * RC * MathF.Sin(radians(2 * d_ro));//(21)
            var dE = MathF.Sqrt(MathF.Pow(dLp / (SL * kL), 2) + MathF.Pow(dCp / (SC * kC), 2) +
                MathF.Pow(dHp / (SH * kH), 2) + RT * (dCp / (SC * kC)) *
                (dHp / (SH * kH))); //(22)

            return dE;
        }

    }

}
